package com.helloscala.web
package snippet

import scala.xml.NodeSeq
import scala.collection.mutable

import net.liftweb.http.{S, SHtml, DispatchSnippet}
import net.liftweb.util.Helpers._
import net.liftweb.common._
import net.liftweb.http.js.{JE, JsCmds, JsCmd}

import yangbajing.util.Imports.Y

import com.helloscala.common.SexCode
import com.helloscala.model._
import net.liftweb.util.CssSel
import net.liftweb.common.Full
import scala.Some
import com.helloscala.model.Account

object UserSnippet extends DispatchSnippet with Loggable {

  def dispatch = {
    case "info" => info
    case "userInfo" => userInfo
    case "userTags" => userTags
    case "moreTags" => moreTags
    case "moreTagsTitle" => moreTagsTitle
    case "datas" => datas
    case "detail" => detail
    case "detailTitle" => detailTitle
    case "popular" => popular
    case "sidebar" => sidebar
    case "changePassword" => changePassword
  }

  def info(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        userId <- S.attr("user_id").or(W.reqTopic.is.map(_.author))
        user <- MUser.findById(userId) ?~ "作者未找到" if W.equalSiteId(user.id)
      } yield {
        "@account-link [href]" #> W.hrefUser(user.id) &
          "@account-name" #> user.nickOrId &
          "@tags *+" #> s" ${user.tagSize}" &
          "@topics *+" #> s" ${user.topicCount}"
      }

    cssSel match {
      case Full(v) =>
        v(nodeSeq)

      case Failure(msg, e, _) =>
        val errorMsg = msg + " " + e.map(_.getLocalizedMessage).openOr("")
        logger.error(errorMsg)
        <h1 class="text-danger">
          {msg}
        </h1>

      case Empty =>
        val msg = "siteId与作者不匹配" + W.siteId
        logger.debug(msg)
        NodeSeq.Empty
    }
  }

  def userInfo(nodeSeq: NodeSeq): NodeSeq =
    S.param("user_id") match {
      case Full(userId) => <div data-lift={"embed?what=/p/user/_user_info;user_id=" + userId}></div>
      case _ => NodeSeq.Empty
    }

  def userTags(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        userId <- H.param("user_id")
      } yield {
        val limit: Int = S.attr("limit") flatMap asInt openOr 10

        "href=more [href]" #> W.hrefUserTag(userId) &
          _tagSel(userId, MTag.findAll(0, limit, author = Some(userId)))
      }

    cssSel map (_ apply nodeSeq) openOr NodeSeq.Empty
  }

  def moreTags(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        userId <- H.param("user_id")
      } yield {
        val curPage = H.param("cur_page", 1)
        val limit = H.param("limit", H.defaultLimit)
        val pagination = MTag.pagination(curPage, limit, author = Some(userId))

        "data-id=tag-size *" #> pagination.total &
          _tagSel(userId, pagination.page) &
          ".pagination" #> pagination.pagination("")
      }

    cssSel map (_ apply nodeSeq) openOr NodeSeq.Empty
  }

  private def _tagSel(userId: String, tags: Seq[Tag]): CssSel =
    ".list-group .list-group-item" #> tags.map {
      tag =>
        ".badge *" #> tag.size &
          "a [href]" #> W.hrefUserTag(userId, tag.tag) &
          "a *" #> tag.tag
    }

  def moreTagsTitle(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      "title -*" #> H.param("user_id").flatMap(MUser.findNickById).openOr("")

    cssSel(nodeSeq)
  }

  def datas(nodeSeq: NodeSeq): NodeSeq = {
    val curPage = H.param("cur_page", 1)
    val limit = H.param("limit", H.defaultLimit)
    val at = H.param("at") or S.param("user_id") map (urlDecode)

    val pagination = MUser.pagination(curPage, limit)

    val cssSel =
      "#user-total *" #> pagination.total &
        ".users" #> (
          ".list-group-item" #> pagination.page.map {
            u =>
              val userHref = W.hrefUser(u.id)
              val userDesc = u.nickOrId

              ".badge *" #> MTopic.size(author = Some(u.id)) &
                ".user-avatar" #> (
                  "a [href]" #> userHref
                  ) &
                ".user-info" #> (
                  ".user-account [href]" #> userHref &
                    ".user-account *" #> u.id &
                    ".user-signature *" #> u.signature.getOrElse("") &
                    ".text-muted *" #> userDesc
                  )
          }) &
        ".pagination" #> pagination.pagination("")

    cssSel(nodeSeq)
  }

  def detail(nodeSeq: NodeSeq): NodeSeq = {
    def funcSafeEmail(v: String): JsCmd =
      if (v == "") JE.Call("formControlSuccess", "#control-safe-email").cmd
      else MUser.checkEmail(v) match {
        case Failure(msg, _, _) => JE.Call("formControlError", "#control-safe-email", msg).cmd
        case _ => JE.Call("formControlSuccess", "#control-safe-email").cmd
      }

    def funcSubmit(user: MUser): JsCmd = {
      val errors = mutable.ListBuffer[JsCmd]()

      for (safeEmail <- user.safe_email)
        MUser.checkEmail(safeEmail) match {
          case Failure(msg, _, _) =>
            errors += JE.Call("formControlError", "#control-safe-email", msg).cmd
          case _ =>
        }

      if (errors.isEmpty) {
        MUser.save(user)
        JsCmds.Alert("修改账户信息成功") & JsCmds.Reload
      } else
        errors.foldLeft[JsCmd](JsCmds.Alert("修改账户信息失败"))(_ & _)
    }

    val cssSel =
      for (
        account <- W.theAccount.is ?~ "用户会话不存在";
        user <- W.reqUser.is if account.id == user.id
      ) yield {
        "#control-id :text [value]" #> account.id &
          "#control-email :text [value]" #> user.email &
          "@email" #> H.uneditableInput(user.email) &
          "@nick" #> SHtml.text(user.nick.getOrElse(""), v => user.nick = Y.option(v)) &
          "@safe-email" #> H.ajaxText(user.safe_email.getOrElse(""), v => funcSafeEmail(v.trim))(v =>
            user.safe_email = Y.option(v)) &
          "@sex" #> H.radioElem[SexCode.Value](SexCode.values, user.sex, v => user.sex = v.toOption) &
          "@submit" #> SHtml.hidden(() => funcSubmit(user))
      }

    cssSel match {
      case Full(sel) =>
        sel(nodeSeq)

      case Failure(msg, e, _) =>
        val errorMsg = e.map(_.getLocalizedMessage) openOr msg
        logger.warn(errorMsg)
        <h2 class="text-warning">
          {errorMsg}
        </h2>

      case _ =>
        <h2 class="text-danger">权限不足</h2>
    }
  }

  def detailTitle(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      "* -*" #> W.reqUser.is.map(_.nickOrId).openOr("")

    cssSel(nodeSeq)
  }

  def popular(nodeSeq: NodeSeq): NodeSeq = {

    val cssSel =
      ".users" #> (
        ".list-group-item" #> MUser.findAllPopular(5).map {
          u =>
            val userHref = W.hrefUser(u.id)

            ".badge *" #> MTopic.size(author = Some(u.id)) &
              ".user-avatar" #> (
                "a [href]" #> userHref
                ) &
              ".user-info" #> (
                ".user-account [href]" #> userHref &
                  ".user-account *" #> u.id &
                  ".user-signature *" #> u.signature.getOrElse("")
                )
        })

    cssSel(nodeSeq)
  }

  def sidebar(nodeSeq: NodeSeq): NodeSeq = {
    val cssSel =
      for {
        account <- W.theAccount.is // if W.equalSiteId(account.id)
        user <- W.reqUser.is if account.id == user.id
      } yield {
        ".panel-title [title]" #> account.id &
          "href=new [href]" #> W.hrefTopicNew(account.id) &
          "href=edit [href]" #> W.hrefUserDetail(account.id) &
          "href=change-password [href]" #> W.href(account.id, "/change_password")
      }

    cssSel.map(_ apply nodeSeq) openOr NodeSeq.Empty
  }

  def changePassword(nodeSeq: NodeSeq): NodeSeq = {
    var oldPassword = ""
    var newPassword = ""
    var repeatPassword = ""

    def funcOld(account: Account, v: String): JsCmd = {
      val password = account.user.password
      if (!password.matchPassword(v))
        JE.Call("formControlError", "#control-old-password", "原密码不匹配").cmd
      else {
        oldPassword = v
        JE.Call("formControlSuccess", "#control-old-password").cmd
      }
    }

    def funcNew(account: Account, v: String): JsCmd = {
      if (!MUser.allowedPassword(v))
        JE.Call("formControlError", "#control-new-password", "新密码不符合要。").cmd
      else {
        newPassword = v
        JE.Call("formControlSuccess", "#control-new-password").cmd
      }
    }

    def funcRepeat(account: Account, v: String): JsCmd = {
      if (!MUser.allowedPassword(v))
        JE.Call("formControlError", "#control-repeat-password", "新密码不符合要。").cmd
      else if (newPassword != v)
        JE.Call("formControlError", "#control-repeat-password", "两次密码不匹配").cmd
      else {
        repeatPassword = v
        JE.Call("formControlSuccess", "#control-repeat-password").cmd
      }
    }

    def funcSubmit(account: Account): JsCmd = {
      if (newPassword != repeatPassword)
        JE.Call("formControlError", "#control-submit", "两次密码不匹配").cmd
      else
        MUser.changePassword(account.id, newPassword) match {
          case Full(_) =>
            W.theAccount.remove()
            JsCmds.Alert("密码修改成功，请使用新密码重新登录。") &
              JsCmds.RedirectTo(W.href("/c/sign_in"))

          case Failure(msg, e, _) =>
            JE.Call("formControlError", "#control-submit", "修改密码失败：" + msg).cmd

          case _ =>
            JE.Call("formControlError", "#control-submit", "修改密码失败").cmd
        }
    }

    val cssSel =
      for {
        userId <- H.param("user_id")
        account <- W.theAccount.is if account.id == userId
      } yield {
        "@old-password" #> SHtml.ajaxText(oldPassword, v => funcOld(account, v), "type" -> "password") &
          "@new-password" #> SHtml.ajaxText(newPassword, v => funcNew(account, v), "type" -> "password") &
          "@repeat-password" #> SHtml.ajaxText(repeatPassword, v => funcRepeat(account, v), "type" -> "password") &
          "@submit" #> SHtml.hidden(() => funcSubmit(account))
      }

    cssSel.map(_ apply nodeSeq) openOr NodeSeq.Empty
  }

}
